<template>
  <div id="graphContainer" class="graph-container">
    <div class="satellite-type">
      <el-select v-model="selectedType" placeholder="请选择卫星关系类型" style="width: 150px" class="select">
        <el-option v-for="item in sateReltions" :key="item" :label="item" :value="item"> </el-option>
      </el-select>
    </div>
    <div class="timeline" v-if="msArr.length > 1">
      <div class="timeline-controls">
        <span class="time-ms">当前日期：{{ formatMs(ms) }}</span>
        <div class="timeline-controls__btns">
          <button class="timeline-btn active" id="btn-play-pause" @click="toggle" title="播放/暂停">
            <span id="play-pause-icon">{{ running ? '⏸️' : '▶️' }}</span>
          </button>
          <button class="timeline-btn" id="btn-rewind" @click="rewindTime" title="后退1天">⏪</button>
          <button class="timeline-btn" id="btn-forward" @click="forwardTime" title="前进1天">⏩</button>
        </div>
      </div>
      <el-slider
        v-model="ms"
        :min="minMs"
        :max="maxMs"
        :step="stepMs"
        :format-tooltip="formatMs"
        @change="handleTimerChange"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { getSatelliteRelations, getSatelliteRelationTypes } from '@/api/dashboard'
import { useLayoutStore } from '@/store/modules/layout'
import G6, { Graph, type EdgeConfig, type GraphData, type NodeConfig } from '@antv/g6'
import dayjs from 'dayjs'
import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
const store = useLayoutStore()

const running = ref(false) // 是否正在自动播放
let timer: number | null = null

/* 播放 / 暂停 切换 */
function toggle() {
  running.value = !running.value
  if (running.value) {
    start()
  } else {
    stop()
  }
}

/* 启动定时器 */
function start() {
  timer = setInterval(() => {
    if (ms.value >= maxMs.value) {
      stop()
      running.value = false
      return
    }
    ms.value += stepMs
  }, 2500)
}

/* 清除定时器 */
function stop() {
  if (timer) {
    clearInterval(timer)
    timer = null
  }
}
/* 后退1天 */
function rewindTime() {
  if (running.value) {
    stop()
  }

  ms.value -= stepMs
}
/* 前进1天 */
function forwardTime() {
  if (running.value) {
    stop()
  }
  ms.value += stepMs
}

let graph: Graph

async function initGraph() {
  const container = document.getElementById('graphContainer')
  if (container) {
    const width = container.scrollWidth || 800
    const height = container.scrollHeight || 500
    graph = new G6.Graph({
      container: 'graphContainer',
      width,
      height,
      defaultNode: {
        size: 2,
        style: {
          fill: '#C6E5FF',
          stroke: '#5B8FF9',
          lineWidth: 0.3,
        },
        labelCfg: {
          position: 'bottom',
          offset: 1,
          style: {
            fill: '#eee',
          },
        },
      },
      defaultEdge: {
        size: 0.1,
        style: {
          lineWidth: 0.1,
          endArrow: {
            path: G6.Arrow.triangle(1.2, 2, 0.2),
            d: 0,
          },
          stroke: '#ccc',
        },
        type: 'line',
        labelCfg: {
          autoRotate: true,
          refY: 1,
          offset: 1,
          style: {
            fill: '#eee',
            fontSize: 1,
          },
        },
      },
      nodeStateStyles: {
        selected: {
          fill: 'steelblue',
          stroke: '#000',
          lineWidth: 1,
        },
        highlight: {
          fill: '#ff7043', // 高亮色
          lineWidth: 3,
          stroke: '#d32f2f',
        },
      },
      modes: {
        default: [
          {
            type: 'zoom-canvas',
            enableOptimize: true,
            optimizeZoom: 0.9,
          },
          {
            type: 'drag-canvas',
            enableOptimize: true,
          },
          'drag-node',
          'brush-select',
        ],
      },
      layout: {
        type: 'fruchterman',
        gravity: 10,
        gpuEnabled: true,
      },
      animate: true,
    })
  }
  loadSatelliteRelations()
}

const msArr = ref<number[]>([])
const minMs = ref<number>(0)
const maxMs = ref<number>(100)
// 步长这里取 1天 24h
const stepMs = 24 * 60 * 60 * 1000
/* ------------------ 4. 滑块当前值 ------------------ */
// 注意：minMs 是一个 ref，初始化 slider 的值时需要使用数值（minMs.value），
// 否则会把整个 ref 对象作为值，导致 v-model 绑定异常，无法拖动。
const ms = ref<number>(minMs.value)

// 当 min/max 变化时，确保当前 ms 在范围内
watch([minMs, maxMs], () => {
  if (ms.value < minMs.value) ms.value = minMs.value
  if (ms.value > maxMs.value) ms.value = maxMs.value
})
let graphData: GraphData = reactive({ nodes: [], edges: [] })

watch(ms, () => {
  handleTimerChange()
})
// 上一次渲染的节点
const oldIds = new Set()

const handleTimerChange = () => {
  const val = ms.value
  const showNodes: NodeConfig[] = []
  const showEdges: EdgeConfig[] = []
  if (!graph) return

  // 如果还没有加载任何边/节点，直接返回
  const allEdges = graphData.edges as EdgeConfig[]
  if (!allEdges || allEdges.length === 0) return

  // 计算哪些边在同一天需要显示
  const visibleNodeIds = new Set<string>()
  allEdges.forEach((edgeItem) => {
    const edgeTime = new Date(String(edgeItem?.time)).getTime()
    const d1 = dayjs(val).format('YYYY-MM-DD')
    const d2 = dayjs(edgeTime).format('YYYY-MM-DD')
    if (edgeTime && d1 == d2) {
      showEdges.push(edgeItem)
      if (edgeItem.source) visibleNodeIds.add(edgeItem.source)
      if (edgeItem.target) visibleNodeIds.add(edgeItem.target)
    }
  })

  // 根据可见边决定哪些节点需要显示
  const allNodes = graphData.nodes as NodeConfig[]
  allNodes.forEach((nodeItem) => {
    if (visibleNodeIds.has(nodeItem.id!)) {
      showNodes.push(nodeItem)
    }
  })

  if (showNodes.length) {
    // 关闭自动重绘，避免中间态闪屏
    graph.setAutoPaint(false)
    // 给第二次渲染数据打标

    graph.changeData({
      nodes: showNodes.map((n) => ({ ...n, isNew: !oldIds.has(n.id) })),
      edges: showEdges,
    })

    // 统一高亮新增节点
    graph.getNodes().forEach((node) => {
      const model = node.getModel()
      if (model.isNew) {
        graph.setItemState(node, 'highlight', true)
      }
    })
    // 恢复自动重绘并一次性 paint
    graph.setAutoPaint(true)
    graph.paint()
    // 保存Id（去重）
    graph.getNodes().forEach((n) => {
      oldIds.add(n.getID())
    })
  }
}
/* ------------------ 5. 辅助函数 ------------------ */
function formatMs(val: number): string {
  // 转回 ISO 字符串并去掉尾巴的秒/毫秒部分
  return dayjs(val).format('YYYY-MM-DD')
}

const loadSatelliteRelations = async () => {
  if (store.activedTaskId) {
    const res = await getSatelliteRelations(store.activedTaskId, selectedType.value)
    if (res.code === 200) {
      if (res.data.relationships) {
        msArr.value = []
        for (const edge of res.data.relationships) {
          if (msArr.value.some((s) => s === new Date(edge.timestamp).getTime())) continue
          msArr.value.push(new Date(edge.timestamp).getTime())
        }
        if (msArr.value) {
          msArr.value = msArr.value.sort((a, b) => {
            return new Date(a).getTime() - new Date(b).getTime()
          })
        }
        if (msArr.value.length > 2) {
          minMs.value = Math.min(...msArr.value)
          maxMs.value = Math.max(...msArr.value)
        } else {
          minMs.value = 0
          maxMs.value = 0
        }
      }
      const nodes: NodeConfig[] = res.data.nodes.map((node) => ({
        id: `Satellite-${node.norad}`,
        label: node.name_en,
        country: node.country,
        launch_place: node.launch_place,
        rocket: node.rocket,
        contractors: node.contractors,
        sat_type: node.sat_type,
        operator: node.operator,
      }))

      const edges: EdgeConfig[] = res.data.relationships.map((edge, idx) => ({
        id: `Satellite-Relation-${idx}`,
        source: `Satellite-${edge.source}`,
        target: `Satellite-${edge.target}`,
        label:
          selectedType.value === '抵近'
            ? `最近距离:${edge.min_distance_km}km  时间:${dayjs(edge.timestamp).format('YYYY-MM-DD HH:mm:ss')}`
            : `${edge.relation} `,
        time: edge.timestamp,
      }))

      nodes.forEach((node: any) => {
        if (!node.labelCfg) {
          node.labelCfg = {}
        }
        node.labelCfg.style = {
          fontSize: 1.3,
        }
        node.degree = 0
        edges.forEach((edge) => {
          if (edge.source === node.id || edge.target === node.id) {
            node.degree++
          }
        })
      })
      mapNodeSize(nodes, 'degree', [1, 15])
      const offsetDiff = 10
      const multiEdgeType = 'line'
      const singleEdgeType = 'line'
      const loopEdgeType = 'loop'
      G6.Util.processParallelEdges(edges, offsetDiff, multiEdgeType, singleEdgeType, loopEdgeType)
      graphData = {
        nodes,
        edges,
      }

      if (!isNaN(minMs.value) && minMs.value !== 0) {
        // 显示第一天的数据
        const showNodes: NodeConfig[] = []
        const showEdges: EdgeConfig[] = []
        const visibleNodeIds = new Set<string>()
        edges.forEach((edgeItem) => {
          const edgeTime = new Date(String(edgeItem?.time)).getTime()
          const d1 = dayjs(minMs.value).format('YYYY-MM-DD')
          const d2 = dayjs(edgeTime).format('YYYY-MM-DD')
          if (edgeTime && d1 == d2) {
            showEdges.push(edgeItem)
            if (edgeItem.source) visibleNodeIds.add(edgeItem.source)
            if (edgeItem.target) visibleNodeIds.add(edgeItem.target)
          }
        })

        nodes.forEach((nodeItem) => {
          if (visibleNodeIds.has(nodeItem.id!)) {
            showNodes.push(nodeItem)
          }
        })
        graph.data({ nodes: showNodes, edges: showEdges })
      } else {
        graph.data(graphData)
      }
      graph.render()
      // 保存Id（去重）
      graph.getNodes().forEach((n) => {
        oldIds.add(n.getID())
      })
    }
  }
}
const mapNodeSize = (nodes: any, propertyName: any, visualRange: any) => {
  let minp = 9999999999
  let maxp = -9999999999
  nodes.forEach((node: any) => {
    node[propertyName] = Math.pow(node[propertyName], 1 / 3)
    minp = node[propertyName] < minp ? node[propertyName] : minp
    maxp = node[propertyName] > maxp ? node[propertyName] : maxp
  })
  const rangepLength = maxp - minp
  const rangevLength = visualRange[1] - visualRange[0]
  nodes.forEach((node: any) => {
    node.size = ((node[propertyName] - minp) / rangepLength) * rangevLength + visualRange[0]
  })
}
watch(
  () => store.activedTaskId,
  () => {
    loadSatelliteRelations()
  }
)

const selectedType = ref('')
const sateReltions = ref<string[]>()
watch(selectedType, () => {
  loadSatelliteRelations()
})
onMounted(() => {
  nextTick(async () => {
    const res = await getSatelliteRelationTypes()
    if (res.code === 200) {
      sateReltions.value = res.data
      selectedType.value = res.data[0]
    }
    initGraph()
    window.addEventListener('resize', () => {
      const container = document.getElementById('graphContainer')
      graph.changeSize(container?.clientWidth!, container?.clientHeight!)
      graph.fitCenter() // 可选：让内容居中
    })
  })
})
onBeforeUnmount(stop)
</script>
<style lang="scss" scoped>
.graph-container {
  position: relative;
  height: 100%;
  width: 100%;
  .timeline {
    width: 96%;
    position: absolute;
    bottom: 20px;
    transform: translate(2%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    .timeline-controls {
      display: flex;
      gap: 10px;
      .time-ms {
        color: aqua;
      }
      .timeline-controls__btns {
        display: flex;
        gap: 2px;
      }
    }
  }
  .legend {
    width: 98%;
    display: flex;
    flex-direction: column-reverse;
    flex-wrap: wrap;
    gap: 10px;
    position: absolute;
    bottom: 5px;
    left: 5px;

    .tag {
      width: 100px;

      &:hover {
        cursor: pointer;
      }
    }
  }
  .satellite-type {
    width: 100%;
    background: linear-gradient(135deg, #27415b 0%, #27415b 100%);
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    .select {
      padding: 5px;
      width: 150px;
    }
  }
}
</style>
